iris-gui: App Store readiness, window-sizing overhaul, and zero-touch NVRAM/MAC setup#38
Merged
Merged
Conversation
eframe's winit 0.30 calls the private SkyLight API CGSSetWindowBackgroundBlurRadius in WindowDelegate::set_blur. IRIS never requests window blur, but the symbol is linked in regardless and Apple's static binary scan rejects it (guideline 2.5.1, submission 2ed07ab1). Vendor a patched winit 0.30.13 (third_party/winit-0.30.13/) that stubs set_blur to a no-op and removes the private extern declarations, wired in via [patch.crates-io]. Only the 0.30 requirement (eframe -> egui-winit -> glutin-winit) matches the patch; iris's own winit 0.29 dependency is the keyboard KeyCode type only and creates no window in iris-gui, so its blur code is dead-stripped. Verified the symbol is gone from the linked binary: nm -u target/release/iris-gui | grep CGSSetWindowBackgroundBlurRadius -> empty Upstream tracking + migration path documented in rules/macos/appstore-private-api.md (winit #4205, milestone 0.31). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Keep both flagged entitlements (per product decision) and add visible, reviewer-testable functionality plus move the one socket-dependent action off the network: - Video-In -> Test Camera: opens the host camera via the same CameraSource the IndyCam/VINO source uses, triggers the macOS camera prompt, and shows a live preview + capture status. Demonstrates com.apple.security.device.camera. (iris-gui/src/camera_test.rs) - Machine -> Serial console...: in-app viewer that connects to the emulator's loopback serial server (127.0.0.1:8881) and streams the live IRIX console (and accepts typed input). The emulator listens (network.server) and the viewer connects (network.client) on loopback. (iris-gui/src/serial_console.rs) - "Send IRIX halt" now types at the console in-process via a new z85c30 channel-B injection queue (Machine::inject_serial_console, Cmd::HaltIrix) instead of opening a TcpStream to 8881, so clean shutdown no longer depends on the serial server socket. - CameraSource now stops cleanly on drop (running flag + Drop join) so the Test Camera window releases the device; previously the capture thread leaked. Entitlement comments updated; paste-ready App Store Connect justifications and reviewer test steps in docs/appstore-review-response.md. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Two display/UX fixes for running at non-integer scale on widescreen monitors: - Framebuffer filtering: the REX3 display texture was always uploaded with NEAREST, so at a fractional device-pixel scale (common once the UI scale is not 100% or the window is freely resized) some emulated pixels were doubled and others not — glyph strokes like the "T" came out uneven. framebuffer_panel now measures the actual device-pixel scale and uploads fb_tex with NEAREST at integer scales (crisp 1x/2x/3x) and LINEAR (bilinear) otherwise. Only the emulator display texture is affected; the egui shell renders independently. Re-uploads only when the integer/fractional state flips, so no per-frame cost. - Left control column: replace the stacked top menu bar + top toolbar + bottom status bar (three rows of vertical chrome) with a single left SidePanel, so the tall 5:4 emulated display gets the full window height — using the spare horizontal real estate on widescreen monitors. The column holds Start/Stop + save-state, the menus (now vertical drop-downs), the Edit-config quick tabs, and a status footer (run-state, MIPS, machine name, dirty COW, toast) pinned to the bottom. Fullscreen auto-hide now reveals on the left edge instead of the top. Window-snap math is orientation-agnostic, so native-scale sizing is unchanged. menu_bar/toolbar/status_bar refactored into menu_list / machine_controls / config_quick_buttons / run_state_label / status_block / control_panel. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Mark the File/Machine/Memory/SCSI/View/Help menu buttons as expandable in the left control column with a trailing ▶, so they read as drop-downs rather than plain buttons. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
In the branch-likely delay-slot failure path, `modified_gprs` was restored to its pre-delay value, but the post-loop flush stores every GPR unconditionally (all_modified = 0xFFFFFFFE), so that value is never read — the compiler flagged it as an unused assignment. Remove the dead restore (and its now-unused capture); the gpr/hi/lo restores stay because the flush does read those. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- Open the window hidden and fit it to the monitor on the first frame before revealing it, so it no longer opens at a default size and visibly resizes. Reveals one frame after the fit settles, with a frame-10 fallback + repaint pump so a missing monitor size can't leave it hidden. - WINDOW_DEFAULT_SIZE → [1512, 1024] to match the left-column running layout (now only the hidden pre-fit fallback). - Zero the CentralPanel inner margin so the emulated display reaches the window edges (keeps the dark fill for the aspect letterbox). - Add a Help → Diagnostics section with Test Camera and Network test (serial console) entries. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- Help → "How camera & networking work…" opens an explainer window with a Camera (IndyCam) and a Networking section, each covering both how to use it (end users) and the host capability / entitlement rationale (App Review). - Help → Diagnostics "Test Camera" and "Network test" are now always shown but enabled only while the Indy is running (disabled-hover prompts to start one). - When the guest becomes safe to stop (cpu_halted — clean shutdown / IRIX halt), auto-release the captured mouse/keyboard (edge-triggered on the running→halted transition, reset at Start) so no Ctrl+Alt+Esc is needed; a toast notes it and clicking the display re-captures. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Remove references to the private sibling "snow" repo and Snow-by-name comparisons from the GUI dialog doc-comments and the mouse-integration rules note. (The toolbox/MacRoman port credit lives on the separate bluescsi-toolbox branch.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…ixes - Status footer shows the live VM-screen scale next to MIPS (1× = native). - New View-menu "VM screen" slider (0.5×–3× in ¼ steps), independent of the UI scale. The window resizes to the chosen scale (clamped to the monitor); the slider is never written back, so dragging it always works and the readout reports the scale actually achieved. - UI scale (zoom) now only grows the controls — the window widens to fit instead of squeezing the display. - Always open windowed; never restore a persisted fullscreen flag (F11 still toggles fullscreen within a session). - Control sidebar is always visible — removed the fullscreen auto-hide that made the toolbar vanish. - Capture the mouse only when the framebuffer image itself is clicked, so navigating menus over the display no longer eats the pointer. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The FrameSink is a persistent slot shared across runs; on Stop→Start it still held the previous run's last frame and seq, so a restarted machine showed the stale screen until its first REX3 frame arrived. Reset the sink worker-side on Cmd::Start (new FrameSink::reset) and drop the GUI's cached texture, so it shows the "waiting for first REX3 frame" placeholder until the new run renders. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…methods) The NVRAM path defaulted to a relative "nvram.bin", resolved against the process working directory — so `cargo run` (repo root) and a bundled .app loaded *different* (often blank, MAC-less) NVRAMs. That's why networking silently broke after changing how the app launches IRIS: a MAC set in one file is absent in the other, so IRIX never attaches ec0. Anchor the GUI's NVRAM to dirs::config_dir()/iris/nvram.bin (next to gui.json; the OS maps it into the sandbox container on the App Store build, so one code path is correct for cargo-run and the bundled app). Relative paths are migrated on load, best-effort copying any existing cwd-relative file so the PROM env (boot settings, any MAC) carries forward. GUI-only; the core CLI default is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
On Start, if the NVRAM has no MAC (heuristic: no xx:xx:xx:xx:xx:xx ASCII in the
file — the PROM stores env values as text), show a movable modal that:
- explains why IRIX networking / System Manager / Disk Manager fail,
- offers a generated SGI-OUI MAC (08:00:69:xx:xx:xx, stable per machine name),
- shows the exact `setenv -f eaddr <mac>` to run at the PROM monitor
(selectable monospace), and
- has a "Save NVRAM (rtc save)" button that drives the iris monitor on
:8888 to persist nvram.bin.
GUI-only; the core's manual setenv/rtc-save workflow is unchanged. Avoids
hand-encoding the firmware-owned NVRAM env format — the real PROM still does the
encoding; we just guide the one command and handle persistence.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
When the NVRAM has no Ethernet MAC, IRIX booting first means the user sets the MAC, then has to reboot — a wasteful double boot. Instead, interrupt the PROM autoboot countdown: inject Esc into the guest keyboard on a cadence over the first ~8s after a no-MAC Start (new input::tap_escape; gated by mac_guard_frames), so the machine drops to the System Maintenance Menu. The user picks the Command Monitor, runs the shown `setenv -f eaddr <mac>`, clicks Save NVRAM (rtc save), and types `boot` — one IRIX boot, no reboot. The guided modal text now reflects this flow, with a fallback note if the interrupt is missed. Best-effort timing (depends on catching the countdown); worth a test boot. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…13a) The MAC is stored as 6 RAW bytes at NVRAM offset 0x13a (SGI OUI 08:00:69 + 3), not the colon-ASCII you type at `setenv` — so the old nvram_has_mac (scanning for "xx:xx:xx:xx:xx:xx" text) always reported "no MAC". Fix detection to read 0x13a. On Start, if the NVRAM has no MAC, write a generated 08:00:69:xx:xx:xx straight into the file (only those 6 bytes; boot env preserved; .bak backup first) before boot — so IRIX attaches ec0 on the first boot, no PROM monitor, no reboot. Removes the fragile Esc-interrupt boot guard (the panic cause) and stops arming the setenv prompt (now a latent, never-shown fallback). NOTE: assumes the PROM reads the MAC from these bytes without a checksum it would reject — needs a test boot to confirm ec0 comes up from a directly-written MAC. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
egui 0.29 reports the *shifted* symbol as its own Key variant, and Pipe (Shift+\) and Questionmark (Shift+/) were the only symbol keys missing from map_key — so typing | or ? sent no scancode to the guest at all. Map them onto the Backslash and Slash physical keys (Shift is forwarded separately, so the guest's keymap forms | and ?). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The auto-write path (write the MAC straight into NVRAM at 0x13a, validated to bring up ec0) handles the no-MAC case silently, so remove the now-unreachable guided-setenv modal (MacModal + its rendering) and send_monitor_command (the rtc-save-over-:8888 helper). One clean path remains: detect the MAC at 0x13a, write a generated one if blank. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
A disk created with a relative path (e.g. scsi3.raw) showed as a bare filename with no hint of where it lives. Resolve PROM/NVRAM/drive paths to absolute (a relative path is joined to the process working dir, where the emulator actually looks) so the summary always shows the full path. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Fresh installs — especially the bundled .app, which has nothing in its working dir for Stage 1's migration to copy — booted a blank NVRAM: no boot env, no MAC. Embed a default NVRAM (the repo's known-good one with the MAC zeroed) and write it to the configured path on Start when no NVRAM exists; the existing auto-write then fills in a per-machine MAC, so the machine boots networked with zero steps. Add a Machine-menu "Reset NVRAM (fresh PRAM)" that restores the default and assigns a fresh MAC (backs the old file up to .bak). NVRAM remains per-machine configurable via the config NVRAM field, so a machine can use its own file. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
"Create blank HDD" defaulted the filename to a relative scsiN.raw, so accepting the default dropped the image in the process working dir (and a relative write fails under the App Store sandbox). Default new disks to <data_dir>/disks/ scsiN.raw — absolute, created on demand, and mapped into the sandbox container so it needs no permission prompt. The picker is seeded there, and "Choose…" still lets the user put a disk anywhere (which grants + bookmarks access on the sandboxed build). Same absolute default for the New Machine blank disk. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The window size is latched at app load (the saved size, or the first-launch fit to vm_scale) and the guest display is letterboxed into it. Only the VM-scale slider and UI zoom re-snap the window after that, so Start no longer triggers a framebuffer snap. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
69af89f to
39fe9b0
Compare
Contributor
Author
|
I just rebased the branch against your most recent changes to main, seems to still be OK. |
This was referenced Jun 18, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
This branch makes the macOS GUI (
iris-gui) shippable on the Mac App Store andmuch friendlier out of the box. It bundles four themes of work:
camera + serial-console features; entitlement justifications.
control column, adaptive framebuffer filtering, VM-screen scale slider.
seeded default NVRAM, and automatic MAC generation so IRIX comes up networked
on first boot with no PROM-monitor steps.
and UX fixes (safe-stop on halt, framebuffer clear on restart, keyboard symbol
mapping, mouse-capture scoping, JIT warning cleanup).
The raw diff is ~64k lines, but ~62k of that is a vendored, lightly-patched copy
of winit 0.30.13 under
third_party/winit-0.30.13/. The actual project changesare ~1,900 lines across 26 files. To review just those:
The only edit to the vendored crate is a one-symbol stub (see §1.1); it is
otherwise an unmodified upstream checkout pinned via
[patch.crates-io].There is an open issue in winit tracking this progress and it has yet to be merged - rust-windowing/winit#4541 so this is why I'm proposing this fix.
1. Mac App Store compliance
1.1 Drop winit's private SkyLight blur API (guideline 2.5.1)
CGSSetWindowBackgroundBlurRadiusinWindowDelegate::set_blur. IRIS never requests window blur, but the symbol islinked in regardless and Apple's static binary scan rejects it.
third_party/winit-0.30.13/) that stubsset_blurto a no-op and removes the privateexterndeclarations, wired invia
[patch.crates-io]inCargo.toml.the patch. iris's own winit 0.29 dependency is the keyboard
KeyCodetype onlyand creates no window, so its blur code is dead-stripped.
nm -u target/release/iris-gui | grep CGSSetWindowBackgroundBlurRadius→ empty.rules/macos/appstore-private-api.md.1.2 Make camera + network.server entitlements reviewer-testable (guideline 2.4.5)
Both flagged entitlements are kept (product decision), but the build now ships
visible, reviewer-testable functionality that exercises each, and moves the one
socket-dependent action off the network:
iris-gui/src/camera_test.rs): opens the hostcamera through the same
CameraSourcethe IndyCam/VINO path uses, triggers themacOS camera prompt, and shows a live preview + capture status — demonstrating
com.apple.security.device.camera.iris-gui/src/serial_console.rs): an in-appviewer that connects to the emulator's loopback serial server
(
127.0.0.1:8881), streams the live IRIX console, and accepts typed input. Theemulator listens (
network.server) and the viewer connects (network.client)on loopback.
channel-B injection queue (
Machine::inject_serial_console,Cmd::HaltIrix)instead of opening a TcpStream to 8881, so clean shutdown no longer depends on
the serial-server socket.
CameraSourcenow stops cleanly on drop (running flag +Dropjoin), so theTest Camera window releases the device; previously the capture thread leaked.
how-to plus the host-capability / entitlement rationale for App Review).
(inbound vs outbound) for each key; paste-ready App Store Connect justifications
and reviewer test steps in
docs/appstore-review-response.md.2. Window sizing / layout overhaul
2.1 Core (
src/ui.rs) — aspect-ratio lock + correct default modeinstead of 1024×768; the renderer snaps to the actual resolution via
resize()once the PROM/IRIX programs its mode.
picture fills it without letterbox bars. New
Ui::aspect_fitkeeps whicheveraxis the user is actively dragging and derives the other; skips when
fullscreen/maximized; 1-px tolerance prevents oscillation. Unit-tested.
MachineConfig.lock_aspect_ratio(serde defaulttrue) lets non-standardmonitors opt into free resizing (display then letterboxes).
display_resis published by the render thread (first frame + on mode change)for the event thread's aspect lock.
2.2 GUI (
iris-gui) — windowed-first, left control column, scalingtoggles within a session).
before revealing (frame-10 fallback + repaint pump so a missing monitor size
can't leave it hidden).
SidePanel) replaces the stacked top menu bar +toolbar + bottom status bar, giving the tall 5:4 display the full window height
and using the spare horizontal space on widescreen monitors. Holds Start/Stop +
save-state, the (now vertical) menus, Edit-config quick tabs, and a status
footer (run-state, MIPS, machine name, dirty COW, toast).
read as drop-downs.
scale; the window resizes to the chosen scale (clamped to the monitor). The
status footer shows the live VM-screen scale next to MIPS (1× = native).
of squeezing the display.
edges (dark fill kept for the aspect letterbox).
the guest display is letterboxed into it; only the scale slider / UI zoom resize.
2.3 Adaptive framebuffer filtering (
iris-gui/src/framebuffer.rs)The REX3 display texture was always uploaded NEAREST, so at fractional
device-pixel scale (common once UI scale ≠ 100% or the window is freely resized)
some emulated pixels were doubled and others not — uneven glyph strokes.
framebuffer_panelnow measures the actual device-pixel scale and uploads NEARESTat integer scales (crisp 1×/2×/3×) and LINEAR otherwise. Only the emulator display
texture is affected; re-uploads only when the integer/fractional state flips, so
no per-frame cost.
2.4 Mouse/menu capture fixes
navigating menus over the display no longer eats the pointer.
the toolbar vanish).
3. Zero-touch NVRAM + Ethernet MAC
Goal: IRIX boots networked on the first Start with no PROM-monitor steps, and
behaves identically whether launched via
cargo runor the bundled.app.3.1 Stable per-user NVRAM path
The NVRAM path defaulted to a relative
nvram.binresolved against the processworking dir, so
cargo run(repo root) and the bundled.apploaded different(often blank) NVRAMs — which is why networking silently broke after changing how
the app launches IRIS. Now anchored to
dirs::config_dir()/iris/nvram.bin(nextto
gui.json; the OS maps it into the sandbox container). Relative paths aremigrated on load (best-effort copy of any existing cwd-relative file so boot env /
MAC carry forward). GUI-only; the core CLI default is unchanged.
3.2 Detect + auto-write the MAC at NVRAM 0x13a
The MAC is 6 RAW bytes at NVRAM offset
0x13a(SGI OUI 08:00:69 + 3), not thecolon-ASCII typed at
setenv— so the old text-scanning detection always reported"no MAC". Detection now reads
0x13a. On Start, if blank, a generated08:00:69:xx:xx:xxis written straight into the file (only those 6 bytes; boot envpreserved;
.bakbackup first) before boot, so IRIX attachesec0on first boot.3.3 Seed a default NVRAM on first run + "Reset NVRAM (fresh PRAM)"
Fresh installs — especially the bundled
.app, whose working dir has nothing forthe migration to copy — booted a blank NVRAM (no boot env, no MAC). An embedded
default NVRAM (the repo's known-good one with the MAC zeroed) is written to the
configured path on Start when none exists; the auto-write (§3.2) then fills the
per-machine MAC, so the machine boots networked with zero steps. New Machine-menu
"Reset NVRAM (fresh PRAM)" restores the default + a fresh MAC (backs the old file
up to
.bak). Addsiris-gui/assets/nvram-default.bin.3.4 Remove the now-dead MAC-setup UX
Earlier iterations of this branch added a guided "no Ethernet MAC" modal, an
Esc-into-PROM boot guard, and a
send_monitor_commandrtc-save helper over :8888.The auto-write path makes all of that unreachable, so it is removed — leaving one
clean path: detect the MAC at
0x13a, write a generated one if blank.3.5 Absolute paths in the machine summary
A disk/PROM/NVRAM created with a relative path showed as a bare filename with no
hint of location. The summary now resolves PROM/NVRAM/drive paths to absolute
(relative joined to the process working dir, where the emulator actually looks).
4. Disk-image management
"Create blank HDD" defaulted to a relative
scsiN.raw, so accepting the defaultdropped the image in the process working dir (and a relative write fails under the
App Store sandbox). New disks now default to
<data_dir>/disks/scsiN.raw—absolute, created on demand, mapped into the sandbox container so it needs no
permission prompt. The picker is seeded there; "Choose…" still lets the user put a
disk anywhere (which grants + bookmarks access on the sandboxed build). Same
absolute default for the New Machine blank disk.
(
iris-gui/src/dialogs/{create_disk.rs,new_machine.rs})5. Correctness & smaller UX fixes
safe_stop.rs, coremachine.rs): newMachine::cpu_is_running()/Status.cpu_halted. When the CPU has halted (cleanshutdown / soft power-off, or idle at the PROM), nothing is writing, so
safe_stop::evaluateshort-circuits to "safe" and skips the force-stop warning.Unit-tested (writable disk while running = unsafe; halted = safe).
transition (reset at Start); a toast notes it, clicking the display re-captures.
No Ctrl+Alt+Esc needed after a clean shutdown.
FrameSinkis a persistent slot sharedacross runs and held the previous run's last frame on Stop→Start. New
FrameSink::reset(worker-side onCmd::Start) + dropping the GUI's cachedtexture shows the "waiting for first REX3 frame" placeholder until the new run
renders.
Key::PipeandKey::Questionmark: egui 0.29 reports the shiftedsymbol as its own
Keyvariant; Pipe (Shift+) and Questionmark (Shift+/) werethe only symbol keys missing from
map_key, so typing|or?sent noscancode. Mapped onto the Backslash/Slash physical keys.
modified_gprsrestore in thebranch-likely delay-slot failure path (the post-loop flush stores every GPR
unconditionally, so the value is never read) — fixes an
unused_assignmentswarning.
GUI dialog doc-comments and generalized the mouse-integration design note
(
rules/gui/gui_mouse_integration.md) to describe the classic-Mac absolute-mousepattern without naming a specific emulator.
Files (excluding vendored winit)
Core (
src/):ui.rs·machine.rs·config.rs·z85c30.rs·camera.rs·camera_nokhwa.rs·camera_v4l.rs·jit/compiler.rs·main.rsGUI (
iris-gui/src/):main.rs(+881) ·settings.rs·serial_console.rs(new) ·camera_test.rs(new) ·config_ui.rs·safe_stop.rs·handle.rs·input.rs·framebuffer.rs·dialogs/{create_disk.rs,new_machine.rs}·assets/nvram-default.bin(new)Build / docs / rules:
Cargo.toml·installer/iris-gui.entitlements·docs/appstore-review-response.md·rules/macos/appstore-private-api.md·rules/gui/gui_mouse_integration.mdTesting / verification
src/ui.rs(aspect_fit),iris-gui/src/safe_stop.rs(haltbehavior).
nm -u target/release/iris-gui | grep CGSSetWindowBackgroundBlurRadius→ empty.cargo checkclean (workspace +iris-gui).Items that still warrant a real test boot before relying on them:
0x13a(§3.2) assumes the PROM reads those byteswithout a checksum it would reject — confirm
ec0comes up on first boot from agenerated MAC.
Risk / compatibility notes
is removable once winit 0.31 ships the upstream fix (tracked in
rules/macos/appstore-private-api.md).lock_aspect_ratiois#[serde(default)], so existingiris.toml/gui.jsonfiles load unchanged.nvram.binrelative) is unchanged.